
#define _GNU_SOURCE
#include<stdio.h>
#include<stdarg.h>
#include<stdlib.h>

//...可变参数可以做函数的形参
//补充知识点：函数栈帧和参数压栈
//当调用函数时，OS会申请一块栈空间用来存储传入给函数的参数，依次从右向左入栈和返回地址
//还要函数内创建的临时变量等等。
//也就是说函数所有的参数存储的空间是连续的通过起始指针和每个参数的类型就可以拿到每个参数了
//从而实现可变参数的功能，最后入栈的参数在存储参数的空间的最开始。
//所以可变参数的函数的第一个参数必须要给具体的类型

//注意：使用时要确保参数类型和数量正确，否则可能导致未定义行为。
void PrintNum(int count,...)
{
    va_list ap;//作用是声明一个可变参数列表对象--就是一个指针
    //初始化列表对象，从存储参数c的栈空间中count参数的地址后面的地址开始取出存储的参数
    //按字节取出存储参数，把所有字节拷贝出来到参数对象ap中
    va_start(ap,count);
    for(int i=0;i<count;i++)
    {
        //从参数对象ap中按int类型拿存储在这些字节中的值
        //比如从头拿出4个字节然后转成int类型返回，下次取就从上次取的结束的位置开始取
        int num=va_arg(ap,int);
        printf("%d ",num);
    }
    va_end(ap);//把指针置空
    printf("\n");
}

//实现printf
void myprintf(const char* fmt,...)
{
    va_list ap;
    va_start(ap,fmt);
    char* res;
    //在堆区申请一块足够大的空间，然后把ap参数列表对象中的参数按照fmt中的格式拿出来
    //比如 fmt="hh%d-%s ffd %c"--从ap中按字照%d即整形取出4个字节然后转成int ,接着从
    //后面继续取出一个字符串的长度的字节转成char*,接着取出一个字节转成char类型
    //然后替换掉fmt中的格式说明符，然后把该字符串放入堆区，把堆区的起始地址通过传入的char**类型的
    //输出型参数返回
    int n=vasprintf(&res,fmt,ap);

    if(n!=-1)
    {
        printf((const char*)res);
        free(res);
    }
    va_end(ap);
   
}

int main()
{
    PrintNum(4,1,2,3,4);
    PrintNum(7,1,2,3,4,5,6,7);
    myprintf("asdsad\n");
    myprintf("as %ddsa %sd\n",10,"哈哈哈");
    printf("asdsad");
    return 0;
}